/* 
 * File:   ObjetoGrafico.cpp
 * 
 * Implementacion de la clase definida en ObjetoGrafico.hpp. Esta define las
 * funciones que permiten la interaccion con los objetos de las escena y su 
 * posicionamiento.
 */

#include "ObjetoGrafico.hpp"

/**
 * Constructor de la Clase ObjetoGrafico
 * 
 * @param pos_x : Posicion en el eje X.
 * @param pos_y : Posicion en el eje Y.
 * @param pos_z : Posicion en el eje Z.
 */
ObjetoGrafico::ObjetoGrafico(GLfloat pos_x, GLfloat pos_y, GLfloat pos_z) {
    this->x = pos_x;
    this->y = pos_y;
    this->z = pos_z;
    this->color = new GLfloat[3];

    //asignar un color por defecto
    this->color[0] = 1.0;
    this->color[1] = 1.0;
    this->color[2] = 1.0;
    this->speed = 1.0;

    //configurar plano de proyeccion para las sombras.
    GLfloat* p1 = new GLfloat[3];
    p1[0] = -1.0;
    p1[1] = SUELO;
    p1[2] = 0.0;

    GLfloat* p2 = new GLfloat[3];
    p2[0] = 1.0;
    p2[1] = SUELO;
    p2[2] = -1.0;

    GLfloat* p3 = new GLfloat[3];
    p3[0] = 1.0;
    p3[1] = SUELO;
    p3[2] = 1.0;

    // posicionar la fuente de luz
    setPosicionLuz(0, 500, -200);
    GLfloat* plane = calcularPlano(p1, p2, p3);

    int n = 0;
    //calcular matriz de sombreado sobre el plano
    CalcMatrizSombra(plane);

    //inicializar las configuraciones de sonido
    initSound();


}

/**
 * Este metodo permite inicializar las configuraciones de Sonido del
 * objeto grafico.
 */
void ObjetoGrafico::initSound() {

    listenerOri = new ALfloat[6];
    listenerOri[0] = 0.0;
    listenerOri[1] = 0.0;
    listenerOri[2] = 1.0;
    listenerOri[3] = 0.0;
    listenerOri[4] = 1.0;
    listenerOri[5] = 0.0;

    listenerPos = new ALfloat[3];
    listenerPos[0] = 0.0;
    listenerPos[1] = 0.0;
    listenerPos[2] = -10.0;

    listenerVel = new ALfloat[3];
    listenerVel[0] = 0.0;
    listenerVel[1] = 0.0;
    listenerVel[2] = 0.0;

    sourcesPos = new ALfloat[3];
    sourcesPos[0] = 0.0;
    sourcesPos[1] = 0.0;
    sourcesPos[2] = -10.0;

    sourcesVel = new ALfloat[3];
    sourcesVel[0] = 0.0;
    sourcesVel[1] = 0.0;
    sourcesVel[2] = 0.0;

    alutInit(0, NULL);



    alListenerfv(AL_POSITION, listenerPos);
    alListenerfv(AL_VELOCITY, listenerVel);
    alListenerfv(AL_ORIENTATION, listenerOri);

    alGetError(); // clear any error messages

    if (alGetError() != AL_NO_ERROR) {
        std::cout << "- Error inicializando buffer!!" << std::endl;
        exit(1);
    } else {
        std::cout << "Sonido  Configurado en objeto" << std::endl;

    }

    // Generar buffers
    alGenBuffers(NUM_BUFFERS, buffer);
}

/**
 * Permite cargar el sonido que le corresponde al objeto.
 * 
 * @param nombre_sonido : Nombre del archivo de sonido.
 * @param loop : Indica si el sonido debe repetirse o debe tener una unica ocurrencia.
 */
void ObjetoGrafico::cargarSonido(char* nombre_sonido, ALboolean loop) {

    ALsizei size, freq;
    ALenum format;
    ALvoid *data;

    ALboolean al_bool;

    alutLoadWAVFile((ALbyte *) nombre_sonido, &format, &data, &size, &freq, &al_bool);
    alBufferData(buffer[0], format, data, size, freq);
    alutUnloadWAV(format, data, size, freq);

    alGetError(); 
    alGenSources(NUM_FUENTES, source);

    if (alGetError() != AL_NO_ERROR) {
        std::cout << "Error creando Fuentes!!" << std::endl;
        exit(2);
    } else {
        std::cout << "Fuentes de Sonido Generadas" << std::endl;
    }

    //configurando sonido
    alSourcef(source[0], AL_PITCH, 1.0f);
    alSourcef(source[0], AL_GAIN, 1.0f);
    alSourcefv(source[0], AL_POSITION, sourcesPos);
    alSourcefv(source[0], AL_VELOCITY, sourcesVel);
    alSourcei(source[0], AL_BUFFER, buffer[0]);
    alSourcei(source[0], AL_LOOPING, loop);


}

/**
 * Este metodo permite activar las configuraciones de blending y proyecciones
 * para poder dibujar sombras mediante la matriz de proyeccion. Todo lo que se
 * dibuje despues  de esta llamada estara afectado por la matriz de sombra.
 */
void ObjetoGrafico::activarModoSombra() {
    glDisable(GL_DEPTH_TEST);

    //se activa el stencil test
    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_EQUAL, 1, ~0);
    glStencilOp(GL_KEEP, GL_KEEP, GL_ZERO);

    glDisable(GL_LIGHTING);

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);


    glColor4f(0.0, 0.0, 0.0, 0.4);


    //se multiplica la matriz de sombra por la matriz de proyeccion actual
    glMultMatrixf((const GLfloat*) matriz_sombra);

  


}

/**
 * Este metodo permite desactivar la configuracion de matriz de sombra.
 */
void ObjetoGrafico::desactivarModoSombra() {

    glDisable(GL_BLEND);
    glEnable(GL_LIGHTING);
    glDisable(GL_STENCIL_TEST);

    glEnable(GL_DEPTH_TEST);

}

//metodo vacio por que se requiere que cada clase que extienda de esta
//lo defina

void ObjetoGrafico::dibujar() {
}

/**
 * Permite mover a la derecha el objeto
 */
void ObjetoGrafico::moverDer() {
    x += speed;
}

/**
 * Permite mover a la izquierda el objeto
 */
void ObjetoGrafico::moverIzq() {
    x -= speed;
}

/**
 * Permite mover hacia arriba el objeto
 */
void ObjetoGrafico::moverArriba() {
    y += speed;
}

/**
 * Permite mover hacia abajo el objeto
 */
void ObjetoGrafico::moverAbajo() {
    y -= speed;
}

/**
 * Permite mover al frente el objeto
 */
void ObjetoGrafico::moverFrente() {
    z -= speed;

}

/**
 * Permite mover hacia atras el objeto
 */
void ObjetoGrafico::moverAtras() {

    z += speed;
}

/**
 * Indicar la velocidad del objeto.
 * 
 * @param s : la velocidad que va a tener el objeto.
 */
void ObjetoGrafico::setSpeed(GLfloat s) {
    speed = s;
}

/**
 * Esta funcion permite obtener los coeficientes de la ecuacion del plano
 * a partir de tres puntos.
 * 
 * @param p1 : El primer punto del plano.
 * @param p2 : El segundo punto del plano.
 * @param p3 : El tercer punto del plano.
 * @return Los coeficientes de la ecuacion del plano.
 */
GLfloat * ObjetoGrafico::calcularPlano(GLfloat* p1, GLfloat* p2, GLfloat * p3) {

    GLfloat * Plano = new GLfloat[4];


    GLfloat longitud;
    GLfloat v1[3], v2[3], v3[3];

    //calcular dos vectores a partir de los puntos
    v1[0] = p2[0] - p1[0];
    v1[1] = p2[1] - p1[1];
    v1[2] = p2[2] - p1[2];

    v2[0] = p3[0] - p2[0];
    v2[1] = p3[1] - p2[1];
    v2[2] = p3[2] - p2[2];

    //hacer el producto cruz
    v3[0] = (v1[1] * v2[2]) - (v1[2] * v2[1]);
    v3[1] = (v1[2] * v2[0]) - (v1[0] * v2[2]);
    v3[2] = (v1[0] * v2[1]) - (v1[1] * v2[0]);
    longitud = (GLfloat) sqrt((v3[0] * v3[0]) + (v3[1] * v3[1]) + (v3[2] * v3[2]));
    if (longitud == 0.0f) longitud = 1.0f;
    v3[0] /= longitud;
    v3[1] /= longitud;
    v3[2] /= longitud;

    Plano[0] = abs(v3[0]);
    Plano[1] = abs(v3[1]);
    Plano[2] = abs(v3[2]);
    Plano[3] = -((Plano[0] * p3[0]) +
            (Plano[1] * p3[1]) +
            (Plano[2] * p3[2]));

    return Plano;
}

/**
 * Permite asignar la posicion de la fuente de luz de la escena para construir
 * la matriz de sombra.
 * 
 * @param pos_x : Posicion en el eje X de la fuente de luz
 * @param pos_y : Posicion en el eje Y de la fuente de luz
 * @param pos_z : Posicion en el eje Z de la fuente de luz
 */
void ObjetoGrafico::setPosicionLuz(GLfloat pos_x, GLfloat pos_y, GLfloat pos_z) {

    posicion_luz[0] = pos_x;
    posicion_luz[1] = pos_y;
    posicion_luz[2] = pos_z;
    posicion_luz[3] = 0.0;
}

/**
 * Este metodo permite calcular la matriz de Sombra, usada para proyectar la
 * sombra del objeto sobre un plano.
 * 
 * @param plane : Los coeficientes de la ecuacion del plano, donde se quiere
 *              proyectar la sombra.
 */
void ObjetoGrafico::CalcMatrizSombra(GLfloat * plane) {



    float dot;

    //calcular el producto punto
    dot = plane[0] * posicion_luz[0] +
            plane[1] * posicion_luz[1] +
            plane[2] * posicion_luz[2] +
            plane[3] * posicion_luz[3];

    //calcular matriz de proyeccion de sombras
    matriz_sombra[0][0] = dot - posicion_luz[0] * plane[0];
    matriz_sombra[1][0] = 0.0 - posicion_luz[0] * plane[1];
    matriz_sombra[2][0] = 0.0 - posicion_luz[0] * plane[2];
    matriz_sombra[3][0] = 0.0 - posicion_luz[0] * plane[3];

    matriz_sombra[0][1] = 0.0 - posicion_luz[1] * plane[0];
    matriz_sombra[1][1] = dot - posicion_luz[1] * plane[1];
    matriz_sombra[2][1] = 0.0 - posicion_luz[1] * plane[2];
    matriz_sombra[3][1] = 0.0 - posicion_luz[1] * plane[3];

    matriz_sombra[0][2] = 0.0 - posicion_luz[2] * plane[0];
    matriz_sombra[1][2] = 0.0 - posicion_luz[2] * plane[1];
    matriz_sombra[2][2] = dot - posicion_luz[2] * plane[2];
    matriz_sombra[3][2] = 0.0 - posicion_luz[2] * plane[3];

    matriz_sombra[0][3] = 0.0 - posicion_luz[3] * plane[0];
    matriz_sombra[1][3] = 0.0 - posicion_luz[3] * plane[1];
    matriz_sombra[2][3] = 0.0 - posicion_luz[3] * plane[2];
    matriz_sombra[3][3] = dot - posicion_luz[3] * plane[3];

    


}

/**
 * Calcular la distacia del objeto a un punto en el espacio.
 * 
 * @param pos_x : La posicion del punto en el eje X.
 * @param pos_y : La posicion del punto en el eje Y.
 * @param pos_z : La posicion del punto en el eje Z.
 * @return La distancia al punto especificado
 */
GLfloat ObjetoGrafico::distanciaA(GLfloat pos_x, GLfloat pos_y, GLfloat pos_z) {
    GLfloat xd = pow(x - pos_x, 2);
    GLfloat yd = pow(y - pos_y, 2);
    GLfloat zd = pow(z - pos_z, 2);

    return sqrt(xd + yd + zd);

}

/**
 * Esta funcion retorma la posicion en el eje X del objeto.
 * @return La posicion en el eje X del objeto
 */
GLfloat ObjetoGrafico::getX() {
    return x;
}

/**
 * Esta funcion retorma la posicion en el eje Y del objeto.
 * @return La posicion en el eje Y del objeto
 */
GLfloat ObjetoGrafico::getY() {
    return y;
}

/**
 * Esta funcion retorma la posicion en el eje Z del objeto.
 * @return La posicion en el eje Z del objeto
 */
GLfloat ObjetoGrafico::getZ() {
    return z;
}





